package tester.parser;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Random;

import messages.BaseMessage;
import messages.persondispatcher.PersonCreationMessage;

import org.xml.sax.Attributes;
import org.xml.sax.SAXException;

import state.Globals;
import state.group.ElevatorStruct;
import state.group.HallCall;
import state.person.Person;
import state.person.PersonState;
import state.person.PersonType;
import tester.input.CreateDelayMessage;
import tester.input.CreateElevatorMessage;
import tester.input.DestroyElevatorMessage;
import tester.outcome.BaseOutcome;
import tester.outcome.group.DeregistrationEventOutcome;
import tester.outcome.group.FloorEventOutcome;
import tester.outcome.group.HallCallEventOutcome;
import tester.outcome.group.RegistrationEventOutcome;
import tester.outcome.person.PersonCreationOutcome;
import tester.outcome.person.PersonGoingInOutcome;
import tester.outcome.person.PersonGoingOutOutcome;
import tester.parser.constants.ParseTestSuiteConstants;
import tester.test.Test;
import tester.test.TestEvent;

/**
 * This class handles the parsing of the xml files
 * @author Caleb Chan
 *
 */
public class TestContentHandler extends ContentHandlerAdapter implements ParseTestSuiteConstants {

	private static final String DEFAULT_NAME = "DEFAULT";
	private static final int DEFAULT_COUNTER_VALUE = 1;
	
	private String personName;
	private PersonType personType;
	private int personWeight;
	private byte personHallcallSource;
	private byte personHallcallDirection;
	private byte personDestination;
	
	private String testName;
	private int testNumber;
	private String testType;
	//private BaseMessage testInputs;
	
	private ArrayList<Test> testSuite;
	
	private ArrayList<BaseOutcome> outcome;
	
	private ArrayList<BaseMessage> testInputs;
	private TestEvent eventHolder;
	private int testIter;
	
	private int eventNumber;
	
	private Hashtable<ArrayList<BaseMessage>, ArrayList<BaseOutcome>> testEvents;
	
	private int defaultPersonCounter; 
	private int defaultOutcomePersonCounter;
	private Test test;
	
	/**
	 * TestContentHandler constructor
	 */
	public TestContentHandler(){
		testEvents = new Hashtable<ArrayList<BaseMessage>, ArrayList<BaseOutcome>>();
		testSuite = new ArrayList<Test>();
		testInputs = new ArrayList<BaseMessage>();
		outcome = new ArrayList<BaseOutcome>();
		defaultPersonCounter = DEFAULT_COUNTER_VALUE;
		defaultOutcomePersonCounter = DEFAULT_COUNTER_VALUE - 1;
		testNumber = DEFAULT_COUNTER_VALUE;
		resetPlaceHolderValues();
	}
	
	/**
	 * This method will returns the test suite
	 * @return Return the list of test
	 */
	public ArrayList<Test> getTestSuite(){
		return testSuite;
	}
	
	/**
	 * This method will reset the state for the whole test
	 */
	private void resetPlaceHolderValues(){
		resetPersonPlaceHolderValues();
		resetTestPlaceHolderValues();
		resetTestEventPlaceHolderValues();
		
	}
	
	/**
	 *  This method will reset the values after each event
	 */
	private void resetTestEventPlaceHolderValues(){
		eventHolder = null;
		eventNumber = 0;
	}
	/**
	 * This method will reset the values after each test
	 */
	private void resetTestPlaceHolderValues(){
		testName = null;
		testType = null;	
	}
	/**
	 * This method will reset the values at the beginning of each test
	 */
	private void resetTestIOHolderValues(){
		testInputs = new ArrayList<BaseMessage>();
		outcome = new ArrayList<BaseOutcome>();
		testEvents = new Hashtable<ArrayList<BaseMessage>, ArrayList<BaseOutcome>>();
		testIter = 1;
	}
	
	/**
	 * This method will reset the values at the end of a person tag
	 */
	private void resetPersonPlaceHolderValues(){
		personName = DEFAULT_NAME;
		personType = PersonType.Basic;
		Random r = new Random();
		personWeight = 50 + r.nextInt(60);
		personHallcallSource = 0x01;
		personHallcallSource = ElevatorStruct.IDLE;
		personDestination = 0x01;
	}
	/**
	 * This method is called when a start tag is found
	 */
	@Override
	public void startElement(String uri, String localName, String qName, Attributes atts) throws SAXException{
		handleStartTestElement(localName);
		handleEventElement(localName, atts);
		handleTesterConfigElement(localName, atts);
		handleOutcomeElement(localName, atts);
		handleStartTestConfigElement(localName, atts);
		handleStartPersonElement(localName, atts);
		handleNonPeopleElements(localName, atts);
	}
	
	/**
	 * This method is called when an end tag is found
	 */
	@Override
	public void endElement(String uri, String localName, String qName) throws SAXException{
		if (localName.equals(INPUT_PERSON_TAG)){
			loadPersonData();
		}else if(localName.equals(TEST_EVENT_TAG)){
			loadTestEventData();
		}else if(localName.equals(TEST_TAG)){
			loadTestData();
		}
	} 
	
	/**
	 * This method will handle the event tags
	 * @param element The tag called
	 * @param atts The attributes associated with the tag
	 * @throws SAXException Error
	 */
	private void handleEventElement(String element, Attributes atts) throws SAXException{
		if (element.equals(TEST_EVENT_TAG)){
			testIter = Integer.parseInt(atts.getValue(EVENT_ITERATION_ATTR));
			boolean waitToFinish = Boolean.parseBoolean(atts.getValue(EVENT_TO_WAIT_ATTR));
			eventHolder = new TestEvent(eventNumber, waitToFinish);
			eventNumber++;
		}
	}
	
	/**
	 * This method will handle the config tag
	 * @param element Config tag
	 * @param atts Attributes associated with the tag
	 * @throws SAXException Error
	 */
	private void handleTesterConfigElement(String element, Attributes atts)throws SAXException{
		if (element.equals(TEST_CONFIG_TAG)){
			
			String ePath = atts.getValue(TESTER_ELEVATOR_PATH_ATTR);
			TesterConfig.getInstance().setElevatorPath(ePath);
			
			int gdPort = Integer.parseInt(atts.getValue(TESTER_GD_PORT_ATTR));
			TesterConfig.getInstance().setGdPort(gdPort);
			
			int pdPort = Integer.parseInt(atts.getValue(TESTER_PD_PORT_ATTR));
			TesterConfig.getInstance().setPdPort(pdPort);
			
			int elevators = Integer.parseInt(atts.getValue(TESTER_INTIAL_ELEVATOR_ATTR));
			TesterConfig.getInstance().setInitalElevators(elevators);
		}
	}
	
	/**
	 * This method will handle all non people input tags
	 * @param element The tag
	 * @param atts Attributes associated with the tag
	 * @throws SAXException Error
	 */
	private void handleNonPeopleElements(String element, Attributes atts) throws SAXException{
		if (element.equals(INPUT_REGISTER_ELEVATOR_TAG)){
			testInputs.add(new CreateElevatorMessage());
		}else if(element.equals(INPUT_DELAY_TAG)){
			int sleepLength = Integer.parseInt(atts.getValue(OUTCOME_DEFAULT_ATTR)) * 1000;
			testInputs.add(new CreateDelayMessage(sleepLength));
		}else if(element.equals(INPUT_KILL_ELEVATOR_TAG)){
			byte elevatorId = Byte.parseByte(atts.getValue(OUTCOME_DEFAULT_ATTR));
			testInputs.add(new DestroyElevatorMessage(elevatorId));
		}
	}
	
	/**
	 * This method will handle the test tag
	 * @param element The tag
	 * @return Returns true if the tag is found
	 * @throws SAXException Error
	 */
	private boolean handleStartTestElement(String element) throws SAXException{
		if (element.equals(TEST_TAG)){
			this.test = new Test(testNumber, Test.DEFAULT_INFINITE_TIMEOUT);
			testNumber++;
			return true;
		}
		return false;
	}
	
	/**
	 * This method will handle the outcome elements
	 * @param element The tag
	 * @param atts The attributes associated with the tag
	 * @return Returns true if the tag is found
	 * @throws SAXException Error
	 */
	private boolean handleOutcomeElement(String element, Attributes atts) throws SAXException{
		if(element.equals(OUTCOME_HALL_CALL_EVENT_TAG)){
			if(atts.getLength() != 3){
				throw new SAXException();
			}
			byte source = Byte.parseByte(atts.getValue(OUTCOME_SOURCE_ATTR));
			byte direction = Byte.parseByte(atts.getValue(OUTCOME_DIRECTION_ATTR));
			outcome.add(new HallCallEventOutcome(0, new HallCall(source, direction), true));
			return true;
		}else if(element.equals(OUTCOME_PERSON_CREATION_EVENT_TAG)){
			if (atts.getLength() != 5){
				throw new SAXException();
			}
			String name = atts.getValue(OUTCOME_NAME_ATTR);
			if (name.equals(DEFAULT_NAME)){
				defaultOutcomePersonCounter++;
				name = "" + defaultOutcomePersonCounter;
			}
			byte type = Byte.parseByte(atts.getValue(OUTCOME_PERSON_TYPE_ATTR));
			byte floor = Byte.parseByte(atts.getValue(OUTCOME_SOURCE_ATTR));
			byte dest = Byte.parseByte(atts.getValue(OUTCOME_DESTINATION_ATTR));
			byte state = Byte.parseByte(atts.getValue(OUTCOME_STATUS_ATTR));
			Person p = new Person(name, PersonType.getPersonTypeForId(type), personWeight, new HallCall(floor, (byte) 0x0), dest);

			p.setState(PersonState.getStateTypeForId(state));
			outcome.add(new PersonCreationOutcome(0, p));
			return true;
		}else if (element.equals(OUTCOME_PERSON_EVENT_TAG)){
			handlePersonOutcomeEventElement(atts);
			return true;
		}else if (element.equals(OUTCOME_REGISTER_EVENT_TAG)){
			byte elevatorId = Byte.parseByte(atts.getValue(OUTCOME_DEFAULT_ATTR));
			outcome.add(new RegistrationEventOutcome(0, elevatorId));
		}else if (element.equals(OUTCOME_DEREGISTER_EVENT_TAG)){
			byte elevatorId = Byte.parseByte(atts.getValue(OUTCOME_DEFAULT_ATTR));
			outcome.add(new DeregistrationEventOutcome(0, elevatorId));
		}else if (element.equals(OUTCOME_FLOOR_EVENT_TAG)){
			byte floor = Byte.parseByte(atts.getValue(OUTCOME_FLOOR_ATTR));
			byte elevatorId = Byte.parseByte(atts.getValue(OUTCOME_ELEVATOR_ATTR));
			outcome.add(new FloorEventOutcome(0, floor, elevatorId));
		}
		return false;
	}
	
	/**
	 * This method will handle the going out and going in events
	 * @param atts The attributes associated with the tag
	 * @throws SAXException Error
	 */
	private void handlePersonOutcomeEventElement(Attributes atts) throws SAXException{
		if (atts.getLength() >= 4){
			throw new SAXException();
		}
		String personName = atts.getValue(OUTCOME_NAME_ATTR);
		if (personName.equals(DEFAULT_NAME)){
			personName = "" + defaultOutcomePersonCounter;
		}
		if (atts.getValue(OUTCOME_EVENT_TYPE_ATTR).equals("GoingInEvent")){
			outcome.add(new PersonGoingInOutcome(0, personName));
		}else if(atts.getValue(OUTCOME_EVENT_TYPE_ATTR).equals("GoingOutEvent")){
			byte floor = Byte.parseByte(atts.getValue(OUTCOME_FLOOR_ATTR));
			outcome.add(new PersonGoingOutOutcome(0, personName));
		}
	}
	
	/**
	 * This method will handle the tags relate to the test settings
	 * @param element The tag
	 * @param atts The attributes associated with the tag
	 * @throws SAXException Error
	 */
	private void handleStartTestConfigElement(String element, Attributes atts) throws SAXException{
		if(element.equals(TEST_NAME)){
			if (atts.getLength() != 1){
				throw new SAXException();
			}
			testName = atts.getValue(OUTCOME_DEFAULT_ATTR);
		}else if(element.equals(TEST_TYPE_TAG)){
			if (atts.getLength() != 1){
				throw new SAXException();
			}
			testType = atts.getValue(OUTCOME_DEFAULT_ATTR);
		}			

	}
	
	/**
	 * This method will handle tags related to creating people
	 * @param element The tag
	 * @param atts The attributes associated with the tag
	 * @throws SAXException Error
	 */
	private void handleStartPersonElement(String element, Attributes atts) throws SAXException{
		if(element.equals(PERSON_NAME_TAG)){
			if (atts.getLength() != 1){
				throw new SAXException();
			}
			personName = atts.getValue(OUTCOME_DEFAULT_ATTR);
		}else if (element.equals(PERSON_TYPE_TAG)){
			if (atts.getLength() != 1){
				throw new SAXException();
			}
			int value = Integer.parseInt(atts.getValue(OUTCOME_DEFAULT_ATTR));
			personType = PersonType.getPersonTypeForId((byte)value);
		}else if (element.equals(PERSON_WEIGHT_TAG)){
			if (atts.getLength() != 1){
				throw new SAXException();
			}
			personWeight = Integer.parseInt(atts.getValue(OUTCOME_DEFAULT_ATTR));
		}else if (element.equals(PERSON_HALL_CALL_SOURCE_TAG)){
			if (atts.getLength() != 1){
				throw new SAXException();
			}
			int value = Integer.parseInt(atts.getValue(OUTCOME_DEFAULT_ATTR));
			personHallcallSource = (byte)value;
		}else if (element.equals(PERSON_HALL_CALL_DIRECTION_TAG)){
			if (atts.getLength() != 1){
				throw new SAXException();
			}
			int value = Integer.parseInt(atts.getValue(OUTCOME_DEFAULT_ATTR));
			personHallcallDirection = (byte)value;
		}else if (element.equals(PERSON_DESTINATION_TAG)){
			if (atts.getLength() != 1){
				throw new SAXException();
			}
			int value = Integer.parseInt(atts.getValue(OUTCOME_DEFAULT_ATTR));
			personDestination = (byte)value;
		}
	}
	
	/**
	 * This method will load the test suite with each test
	 */
	private void loadTestData(){
		test.setTestName(testName);
		test.setTestType(testType);
		test.addEvents(testEvents);
		testSuite.add(test);
		resetPlaceHolderValues();
	}
	
	/**
	 * This method will load the person into each test
	 */
	private void loadPersonData(){
		if (personName.equals(DEFAULT_NAME)){
			personName = "" + defaultPersonCounter;
			defaultPersonCounter++;
		}
		HallCall hallCall = new HallCall(personHallcallSource, personHallcallDirection);
		Person p = new Person(personName, personType, personWeight, hallCall, personDestination);
		Globals.getLogger().log("Name : " + personName, this.getClass().getName(), "loadPersonData");
		Globals.getLogger().log("Type : " + personType, this.getClass().getName(), "loadPersonData");
		Globals.getLogger().log("Weight : " + personWeight, this.getClass().getName(), "loadPersonData");
		Globals.getLogger().log("Source : " + personHallcallSource, this.getClass().getName(), "loadPersonData");
		Globals.getLogger().log("Direction : " + personHallcallDirection, this.getClass().getName(), "loadPersonData");
		Globals.getLogger().log("Dest : " + personDestination, this.getClass().getName(), "loadPersonData");
		try {
			testInputs.add(new PersonCreationMessage(p));
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		resetPersonPlaceHolderValues();
	}
	
	/**
	 * This method will load each outcome into the test
	 */
	private void loadTestEventData(){	
		if (testIter < 1){
			testIter = 1;
		}
		testEvents.put(testInputs, outcome);
		eventHolder.addEvents(testEvents);
		for(int i = 0; i < testIter; i++){
			test.addEvents(new TestEvent(eventHolder, eventNumber));
			eventNumber++;
		}
		
		resetTestIOHolderValues();
	}
	
	
}
